package org.hawk.gwt.ppc;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Collections;

import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
/**
 * Logger subsystem for preprocessor.
 * Default implementation delegates logging to the GWT
 * {@link PrintWriterTreeLogger}.
 * @author alex.bereznevatiy@gmail.com
 */
public class Log {
	
	/**
     * Logs an error.
     */
	private static final int LOG_ERROR = 0x0F;

    /**
     * Logs a warning.
     */
	private static final int LOG_WARN = 0x01F;

    /**
     * Logs information.
     */
	private static final int LOG_INFO = 0x03F;

    /**
     * Logs information related to lower-level operation.
     */
	private static final int LOG_TRACE = 0x07F;

    /**
     * Logs detailed information that could be useful during debugging.
     */
	private static final int LOG_DEBUG = 0x0FF;

	
	/**
	 * Provides available log engines.
	 * 
	 * @author alex.bereznevatiy@gmail.com
	 */
	public static enum LogEngine {
		
		/**
		 * Default engine logs messages with GWT PrintWriterTreeLogger.
		 */
		DEFAULT {
			private Object logger;
			
			private Map<Integer, ?> typeMap;
			
			private PrintWriterTreeLogger getLogger() {
				if (logger == null) {
					PrintWriterTreeLogger logger = new PrintWriterTreeLogger();
					Configuration conf = Environment.getInstance().getConfiguration();
					logger.setMaxDetail(Type.valueOf(conf.get("-logLevel", Type.INFO.name())));
					this.logger = logger;
					
					Map<Integer, Type> m = new HashMap<Integer, Type>();
					m.put(LOG_ERROR, Type.ERROR);
					m.put(LOG_WARN, Type.WARN);
					m.put(LOG_INFO, Type.INFO);
					m.put(LOG_TRACE, Type.TRACE);
					m.put(LOG_DEBUG, Type.DEBUG);
					
					typeMap = Collections.unmodifiableMap(m);
				}
				return (PrintWriterTreeLogger) logger;
			}
			
			void log(int level, String message) {
				PrintWriterTreeLogger logger = getLogger();
				Type type = (Type) typeMap.get(level);
				if (type == null) {
					type = Type.DEBUG;
				}
				logger.log(type, message);
			}
			
			void log(int level, String message, Throwable th) {
				PrintWriterTreeLogger logger = getLogger();
				Type type = (Type) typeMap.get(level);
				if (type == null) {
					type = Type.DEBUG;
				}
				logger.log(type, message, th);
			}
			
			boolean isLoggable(int level) {
				PrintWriterTreeLogger logger = getLogger();
				Type type = (Type) typeMap.get(level);
				if (type == null) {
					type = Type.DEBUG;
				}
				return logger.isLoggable(type);
			}
		},
		
		/**
		 * Reflection bridge to maven logger.
		 */
		MAVEN {
			
			private Object logger;
			private Method logMethod;
			private Method isLogEnabmedMethod;
			
			private void initIfNecessary() throws Exception {
				if (isLogEnabmedMethod != null) {
					return;
				}
				
				Class<?> clazz = Class.forName("org.hawk.gwt.ppc.mojo.Logger");
				logger = clazz.getMethod("get").invoke(null);
				logMethod = clazz.getMethod("log", int.class, String.class, Throwable.class);
				isLogEnabmedMethod = clazz.getMethod("isEnabled", int.class);
			}

			@Override
			void log(int level, String message) {
				log(level, message, null);
			}

			@Override
			void log(int level, String message, Throwable th) {
				try {
					initIfNecessary();
					logMethod.invoke(logger, level, message, th);
				} catch (Exception ex) {
					throw new RuntimeException(ex);
				}
			}

			@Override
			boolean isLoggable(int level) {
				try {
					initIfNecessary();
					return (Boolean)isLogEnabmedMethod.invoke(logger, level);
				} catch (Exception ex) {
					throw new RuntimeException(ex);
				}
			}
			
		};
		
		abstract void log(int level, String message);
		
		abstract void log(int level, String message, Throwable th);

		abstract boolean isLoggable(int level);
	}
	
	private Log(){}
	
	private static LogEngine engine;
	
	/**
	 * Sets engine for logging. This allows to override the default
	 * behavior when logs are printed to stdout with GWT PrintWriterTreeLogger.
	 * Note that this method works only in the initialization time when no messages
	 * are logged. In other cases call to this method will cause exception.
	 * 
	 * @param engine to override default one
	 * 
	 * @throws NullPointerException if parameter is <code>null</code>.
	 * @throws IllegalStateException if engine is already set or some messages are logged.
	 */
	static void setEngine(LogEngine engine) {
		if (engine == null) {
			throw new NullPointerException();
		}
		if (engine != null && Log.engine == null) {
			Log.engine = engine;
		} else {
			throw new IllegalStateException("Unable to override engine - it is already set.");
		}
	}
	
	private static LogEngine getLogger() {
		if (engine == null) {
			engine = LogEngine.DEFAULT;
		}
		return engine;
	}
	
	/**
	 * Logs debug message
	 * @param msg
	 */
	public static void debug(Object msg) {
		getLogger().log(LOG_DEBUG, String.valueOf(msg));
	}
	
	/**
	 * @return <code>true</code> if debug level of logging is active.
	 */
	public static boolean isDebugEnabled(){
		return getLogger().isLoggable(LOG_DEBUG);
	}
	
	/**
	 * Logs trace message
	 * @param msg
	 */
	public static void trace(Object msg) {
		getLogger().log(LOG_TRACE, String.valueOf(msg));
	}
	
	/**
	 * Logs info message.
	 * @param message
	 */
	public static void info(Object msg) {
		getLogger().log(LOG_INFO, String.valueOf(msg));
	}
	
	/**
	 * Logs warning message.
	 * @param message
	 */
	public static void warn(Object msg) {
		getLogger().log(LOG_WARN, String.valueOf(msg));
	}
	
	/**
	 * Logs error message.
	 * @param message
	 */
	public static void error(Object msg) {
		getLogger().log(LOG_ERROR, String
			.valueOf(msg).replace("\n", "\n\t"));
	}

	/**
	 * Logs error message with exception traces.
	 * @param message
	 * @param throwable
	 */
	public static void error(Object msg, Throwable th) {
		String message = String.valueOf(msg).replace("\n", "\n\t");
		getLogger().log(LOG_ERROR, message, th);
	}
	
	/**
	 * Reports common internal error.
	 * @param msg
	 * @param th
	 */
	public static void internalError(String msg, Throwable th) {
		error(msg+"Please report about this exception to " +
		"alex.bereznevatiy@gmail.com to improve the quality of " +
		"GWT preprocessor.\nIf you are using some complex expression " +
		"you may split it on simple parts as temporary solution.\n" +
		"For example:\n" +
		"\tClass.forName(str).getMethod(str2).getClass();\n" +
		"can be split on:\n" +
		"\tClass<?> cls = Class.forName(str);\n" +
		"\tMethod m = cls.getMethod(str2);\n" +
		"\tClass<?> cls2 = m.getClass();\n" +
		"Most probably such workaround will help you.\nAnyway do not hesitate " +
		"to contact alex.bereznevatiy@gmail.com for any kind of help.",th);
		
	}
	
	/**
	 * Prints common information about preprocessor.
	 */
	public static void printHeader() {
		info("GWT Preprocessor (v" + Configuration.PREPROCESSOR_VERSION + ")");
		
		info(arg(Configuration.REFLECTION_PARAMETER, "nothing to process (default)"));
		Configuration config = Environment.getInstance().getConfiguration();
		String defaultReflect = config.get(Configuration.REFLECTION_PARAMETER, "nothing to process") + " (default)";
		
		info(arg(Configuration.PREPROCESS_PARAMETER, defaultReflect));
		info(arg(Configuration.PATTERN_PARAMETER, "ant (default)", " pattern"));
		info(arg(Configuration.GENERIC_SUPPORT_PARAMETER, "false (default)"));
		info(arg(Configuration.PROCESS_ANNOTATIONS_PARAMETER, "false (default)"));
		info(arg(Configuration.KEEP_PREPROCESSED_SOURCES_PARAMETER, "false (default)"));
	}
	
	private static String arg(String name, String defaultValue, String...sufixes) {
		Configuration config = Environment.getInstance().getConfiguration();
		StringBuilder result = new StringBuilder();
		result.append("  -P");
		result.append(name);
		while (result.length() < 30) {
			result.append(' ');
		}
		result.append("= ");
		result.append(config.get(name, defaultValue));
		for (String sufix : sufixes) {
			result.append(sufix);
		}
		return result.toString();
	}
}
